package org.dt.common.sequence.spring.client;

import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.ProxyMethodInvocation;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.RootClassFilter;
import org.springframework.aop.support.StaticMethodMatcher;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.BiPredicate;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toSet;

/**
 * 为SpringBean提供基于列表的缓存Aop增强的抽象类
 * <p>
 * 当缓存水位低于 {@code numberPerRequest * loadFactor}，触发同步补充缓存操作
 * <p>
 * {@link AbstractSyncListCacheForSequenceClient#isSofaReferenceBean} 判断是否为SofaReferenceBean
 *
 * @author yujiadong
 * @create: 2019/02/22 16:05
 **/
@Slf4j
abstract public class AbstractSyncListCacheForSequenceClient<T> extends AbstractBeanFactoryAwareAdvisingPostProcessor implements MethodInterceptor {

    protected final float  loadFactor;
    protected final int    numberPerRequest;
    protected final Method proxiedMethod;

    protected final BiPredicate<Object, String> isSofaReferenceBean = new BiPredicate<Object, String>() {
        @Override
        public boolean test(Object bean, String s) {
            return proxiedMethod.getDeclaringClass().isAssignableFrom(bean.getClass())
                   && bean instanceof Advised
                   && Arrays.stream(((Advised) bean).getAdvisors()).anyMatch(
                    x -> x.getAdvice().toString().startsWith("com.alipay.sofa.runtime.service.binding.JvmBindingAdapter$JvmServiceInvoker"));
        }
    };
    private         LinkedBlockingQueue<T>      cachePool           = new LinkedBlockingQueue<>();

    public AbstractSyncListCacheForSequenceClient(Method method, int numberPerRequest, float loadFactor) {
        proxiedMethod         = method;
        this.numberPerRequest = numberPerRequest;
        this.loadFactor       = loadFactor;
    }

    private static Pointcut createPointcut(Method proxiedMethod) {
        return new Pointcut() {
            @Override
            public ClassFilter getClassFilter() {
                return new RootClassFilter(proxiedMethod.getDeclaringClass());
            }

            @Override
            public MethodMatcher getMethodMatcher() {
                return new StaticMethodMatcher() {
                    @Override
                    public boolean matches(Method method, Class<?> targetClass) {
                        return method.equals(proxiedMethod);
                    }
                };
            }
        };
    }

    /**
     * 返回已缓存的数量
     *
     * @return 已缓存的数量
     */
    public int cachePoolSize() {
        return cachePool.size();
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (!Objects.equals(proxiedMethod, invocation.getMethod())) {
            return invocation.proceed();
        }

        final Integer userNeeded   = extractApplyNumber(invocation.getArguments());
        final Integer actualNeeded = Math.max(userNeeded, numberPerRequest);

        if (!(invocation instanceof ProxyMethodInvocation)) {
            log.error("不支持的invocation类型 {}, 该Cache将不会生效", invocation.getClass());
            return invocation.proceed();
        }

        if (Objects.equals(actualNeeded, userNeeded)) {
            log.warn("该缓存应该用于单次请求数量远小于{}的场景，否则无法起到预定的优化效果。建议调大numberPerRequest或停止使用该缓存", numberPerRequest);
        }

        log.debug("acquire: {}, current cache size: {}", userNeeded, cachePool.size());

        ((ProxyMethodInvocation) invocation).setArguments(buildTargetArguments(actualNeeded, invocation.getArguments()));

        final List<T> list = new ArrayList<>(userNeeded);
        int           need = userNeeded;
        while (need > 0) {
            if (cachePool.size() <= numberPerRequest * loadFactor) {
                log.info("当前缓存水位过低，将补充 {} 个缓存", actualNeeded);
                cachePool.addAll(castOriginResult(invocation.proceed()));
            }
            need -= cachePool.drainTo(list, need);
        }
        return list;

    }

    @SuppressWarnings("Duplicates")
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        //skip adding redundant advisors
        //for example, when processing ServiceImpl and SofaServiceImpl(both implements the same interface)
        boolean skipPostProcessAfterInitialization = false;
        if (bean instanceof Advised) {
            Advised advised = (Advised) bean;
            skipPostProcessAfterInitialization = Stream.of(advised.getAdvisors()).collect(toSet()).contains(this.advisor);
        }

        if (!skipPostProcessAfterInitialization && support(bean, beanName)) {
            this.advisor = new DefaultPointcutAdvisor(createPointcut(proxiedMethod), this);
            log.info("找到可增强的bean {} ", beanName);
            return super.postProcessAfterInitialization(bean, beanName);
        } else {
            return bean;
        }

    }

    /**
     * 构造被代理方法的入参
     *
     * @param actualNeeded 修改后的需要申请的数量
     * @param originArgs   原始的入参列表
     * @return 入参列表
     */
    protected abstract Object[] buildTargetArguments(Integer actualNeeded, Object[] originArgs);

    /**
     * 把被代理方法返回的结果转为列表
     *
     * @param originReturnResult 原始的返回结果
     * @return 列表
     */
    protected abstract List<T> castOriginResult(Object originReturnResult);

    /**
     * 从入参列表中找到需要的参数
     *
     * @param originArgs 原始的入参列表
     * @return 需要申请的数量
     */
    protected abstract Integer extractApplyNumber(Object[] originArgs);

    /**
     * 判断bean是否要增强
     *
     * @param bean     bean
     * @param beanName beanName
     * @return 判断bean是否要增强
     */
    protected boolean support(Object bean, String beanName) {
        if (isSofaReferenceBean.test(bean, beanName)) {
            this.setBeforeExistingAdvisors(true);
            return true;
        }
        return false;
    }


}
